# 19. 函数名,闭包,迭代器


# 函数名的运用

函数名也是一个变量,但它也是一个特殊的变量,和括号配合可以执行函数的变量

这样说,函数名中的值也不是实际的值,只是存储了这些值所在的内存地址


# 函数名的内存地址

如果不用括号的话,那就会打印出内存地址

def so():
    print("人生的意义")
print(so)
print(so())

执行结果:
<function so at 0x00000162176E1EA0>
人生的意义
None

​ 以上实例,可以看得出,如果不加括号的结果,和加了括号的结果,下面的None,很熟悉了吧,这是函数的默认返回 值,因为使用print()这个函数使用函数,会默认调用函数的返回值


# 函数名赋值变量

可以通过赋值的方式,让变量接收到函数中的内存地址,从而能调用函数

def so():
    print("人生的意义")
wo = so
wo()

执行结果:
人生的意义

​ 以上实例,也可以这样理解,在使用print()函数执行函数的时候不加括号,这样就会得到函数的内存地址,上面的变量 赋值也是这样的原理,拿到函数的内存地址赋值到变量中,这样变量就可以像函数一样调用函数,函数名==变量


# 函数名可以当做容器类型的元素使用

函数名可以当成变量在容器类型中使用

def qo():
    print("人生的意义")
def wo():
    print("方向在那里")
def eo():
    print("是不是每次的选择都代表的以后方向")
def ro():
    print("如果是,人一生中会遇到无数中选择")
## 第一种方式
so = [qo,wo,eo,ro]
print(so)
## 第二种方式
so = [qo(),wo(),eo(),ro()]
print(so)
## 第三种方式
so = [qo,wo,eo,ro]
for i in so:
    i()
    
执行结果:
## 第一种执行结果
[<function qo at 0x000002926BFC1EA0>, <function wo at 0x000002926C19BA60>, <function eo at 0x000002926C19BD08>, <function ro at 0x000002926C19BD90>]
## 第二种执行结果
人生的意义
方向在那里
是不是每次的选择都代表的以后方向
如果是,人一生中会遇到无数中选择
[None, None, None, None]
## 第三种执行结果
人生的意义
方向在那里
是不是每次的选择都代表的以后方向
如果是,人一生中会遇到无数中选择

以上实例:

1. 第一种方式:使用列表并将函数放在里面不加括号,这样子使用print()函数只会输出函数的内存地址
2. 第二种方式:使用列表并将函数放在里面加括号,这样子使用print()函数只会输出函数的返回值,那为什么会有函数的数据,因为Python解释器在调用函数时会先执行一遍。
3. 第三种方式:可以使用for循环,每次循环到的函数在加括号执行一遍

# 函数可以当做函数的参数

函数是可以当成参数给另一个函数的值,函数 == 变量

def wo():
    print("方向在那里")
def so(wo):
    print("人生的意义")
    wo()
so(wo)

执行结果:
人生的意义
方向在那里

​ 以上实例,也没什么可以说的了,这种写法很少人写,遇到的话,很幸运


# 函数名作为函数的返回值

函数是可以作为返回值,返回给调用者,函数 == 变量

def so():
    print("方向在那里")
    def wo():
        print("人生的意义")
    return wo()
so()

执行结果:
方向在那里
人生的意义

​ 以上实例, 很强调了函数也是属于变量的一种,如果返回值能返回变量,那为什么不能返回函数呢



# 闭包

闭包就是内层函数对外层函数(非全局)的变量的引用

可以让一个局部变量常驻内存

闭包可以保护一个变量,不被修改污染


# 闭包

def so():
    se = "人生的阳光灿烂的日子"
    def eo():
        print(se)
    eo()
so()

执行结果:
人生的阳光灿烂的日子

​ 以上实例,闭包只要是内层函数对外层函数变量的调用就是闭包,非全局


# 通过内置函数查看函数是否是闭包

可以通过 closure 来检测函数是否是闭包

如果不是闭包返回:None

def so():
    se = "人生的阳光灿烂的日子"
    def eo():
        print(se)
    eo()
    print(eo.__closure__)
so()

执行结果:
人生的阳光灿烂的日子
(<cell at 0x00000264B1766648: str object at 0x00000264B17E0810>,)

​ 以上实例,如果是闭包就返回一堆值,如果不是就返回None


# 在函数外部调用内部函数的变量

def so():
    se = "人生的阳光灿烂的日子"
    def eo():
        print(se)
    return eo
wo = so()
wo()

执行结果:
人生的阳光灿烂的日子

​ 以上实例,为什么在函数执行完毕变量会没被删除,之前文章中也写到了,函数执行完毕,内部数据会被删除,也差不 多这意思,在这里要补充一点,如果Python解释器在执行函数的时候发现内部变量可能有外部调用,那Python解释器默 认会把这变量常驻内存


# 在函数外部调用内部函数的变量(多层嵌套)

def so():
    se = "人生的阳光灿烂的日子"
    def eo():
        def ro():
            print(se)
        print(se)
        return ro
    return eo
wo = so()()
wo()

执行结果:
人生的阳光灿烂的日子
人生的阳光灿烂的日子

# 关于闭包的好处-纯文字-可略过

在外部可以访问内部函数,这时候内部函数访问的时间和时机就不一定,因为外部可以选择任意的时间去访问内部函数,想一下,之前文章写过的,一个函数执行完毕,则这个函数中的变量以及局部命名空间中的内容都会被删除,在闭包中,如果变量被删除,寻内部函数将不能正常执行,所以Python内部规定,如果在内部函数中访问了外层函数中的变量,那么这个就是将不会被删除,还会常驻内存中,也是说,使用闭包可以保证外层函数中的变量在内存中常驻

闭包的作用就是让⼀个变量能够常驻内存. 供后面的程序使用


# 一个简单的爬虫的代码

from urllib.request import urlopen
def so():
    en = urlopen("http://92.linux91.cn/index.php/archives/111/").read()
    def wo():
        return en
    return wo
ss = so()
en = ss()
print(en)
es = ss()
print(es)

执行结果:
执行结果我就不写出来了,太长了

代码行意:
引用urllib模块中的urlopen函数
创建一个函数
	通过urlopen爬取指定网址的信息,并读取到内存中,赋值给变量
    创建一个函数
    	设置一个返回值,返回上层的变量
    设置一个返回值,返回下层函数
将函数赋值给变量,赋值内存地址
将获取到的内容赋值给一个变量,为什么不是内存地址,因为赋值要先把函数执行一遍
使用print()执行变量
在次获取到的内容赋值给一个变量,为什么不是内存地址,因为赋值要先把函数执行一遍
使用print()执行变量


# 迭代器

之前文件一直都有写到可迭代对象进行迭代操作,那么到底什么才是可迭代对象

可迭代对象:字符串,列表,元组,字典,集合

为什么可以称为可迭代对象呢,因为他们都遵循了可迭代协议


# 使用内置函数查看是否可迭代对象 - dir()

怎么知道数据或数据类型是不是可迭代对象,就要使用dir()是否包含__iter__ 函数的方法

iter 函数称为:iterable表示可迭代的,表示可迭代协议

so = "你好呀,今天天气真不错"
print(dir(so))

执行结果:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

​ 以上实例,就要从中找到是否包含__iter__ 函数


# 整形

so = 1111
print("__iter__" in dir(so))

执行结果:
False

​ 以上实例,整形中的方法没有__iter__ 函数


# 字符串

so = "你好呀,今天天气真不错"
print("__iter__" in dir(so))

执行结果:
True

​ 以上实例,字符串中的方法有__iter__ 函数


# 列表

so = [1,"你好",2,"今天天气怎么样"]
print("__iter__" in dir(so))

执行结果:
True

​ 以上实例,列表中的方法有__iter__ 函数


# 元组

so = (1,"你好",2,"今天天气怎么样")
print("__iter__" in dir(so))

执行结果:
True

​ 以上实例,元组中的方法有__iter__ 函数


# 字典

so = {1:"真不错",2:"你确实吗"}
print("__iter__" in dir(so))

执行结果:
True

​ 以上实例,字典中的方法有__iter__ 函数


# 集合

so = {"哈喽","您好"}
print("__iter__" in dir(so))

执行结果:
True

​ 以上实例,集合中的方法有__iter__ 函数


# 查看文件操作是否为可迭代对象

so = open("文件",mode="r",encoding="utf-8")
wo = so.read()
print("__iter__" in dir(wo))

执行结果:
True

​ 上实例,文件操作中的方法有__iter__ 函数


# 还可以使用另一种方式来查看 - isinstence()

可以通过isinstence()函数加上iterable来查看对象是否可迭代对象

可以通过isinstence()函数加上iterator来查看对象是否是迭代器

Iterable: 可迭代对象. 内部包含__iter__()函数

Iterator: 迭代器. 内部包含__iter__() 同时包含__next__()

from collections import Iterable
from collections import Iterator
so = [1,2,3,4,5,6,7,8]
li = so.__iter__()
print(isinstance(so,Iterable))
print(isinstance(so,Iterator))
print(isinstance(li,Iterable))
print(isinstance(li,Iterator))

执行结果:
True
False
True
True

​ 以上实例,查看so跟li变量是否是可迭代对象跟迭代器

​ 可以确定. 如果对象中有__iter__函数. 那么我们认为这个对象遵守了可迭代协议. 就可以获取到相应的迭代器. 这里的__iter__是帮助我们获取到对象的迭代器


# 使用内置迭代器来获取内容 - next

使用__next__来获取__iter__中的内容

so = "今天天气怎么样"
li = so.__iter__()
print(li.__next__())
print(li.__next__())
print(li.__next__())
print(li.__next__())
print(li.__next__())
print(li.__next__())
print(li.__next__())

执行结果:
今
天
天
气
怎
么
样

​ 以上实例,如果使用__next__来获取一旦获取超过值的话会报错

so = ""
li = so.__iter__()
print(li.__next__())

执行结果:
StopIteration

​ 以上实例,就是__next__获取不到值后报错StopIteration


# 模拟for循环机制

so = "今天天气怎么样"
li = so.__iter__()
while 1:
    try:
        it = li.__next__()
        print(it)
    except StopIteration:
        break
        
执行结果:
今
天
天
气
怎
么
样

代码行义解意
创建一个字符串变量
加载可迭代对象函数并赋值给变量
while循环-无限
	获取元素:
        加载迭代器并赋值给变量
        打印变量
    如果遇到StopIteration这个错误:
        退出循环

# 迭代器总结 - 全文本 - 可略过

Iterable: 可迭代对象. 内部包含__iter__()函数

Iterator: 迭代器. 内部包含__iter__() 同时包含__next__()

迭代器特点:

  1. 节省内存

    1. 惰性机制(如果不去取值,它就不会返回值)
    2. 不能反复,只能向下执行

可以这样理解,迭代的内容是子弹,iter()就是弹夹,next()就是枪

把子弹装入弹夹中,在用枪一个子弹(获取到的值)一一打出

for循环的时候,也是一开始就是用__iter__()来获取迭代器的,后面每次获取元素都是通过__next__()来完成的,当程度遇到StopIteration错误,就结束循环